﻿using DDBS_Server.Models;
using Raven.Abstractions.Indexing;
using Raven.Client;
using Raven.Client.Document;
using Raven.Client.FileSystem;
using Raven.Client.Shard;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Web;

namespace DDBS_Server.APIControllers
{
    public partial class RavenDB
    {
        Dictionary<string, IDocumentStore> shardStores;
        List<Shard> shards;
        ShardStrategy shardStrategy;
        IDocumentStore store;
        IFilesStore fstore;

        // Sharded.
        public RavenDB()
        {
            // Load shard list.
            using (IDocumentSession session = RavenContext.CreateSession())
            {
                try
                {
                    shards = session.Query<Shard>().ToList();
                    if (shards.Count == 0) throw new Exception(); // no database in list
                }
                catch
                {
                    // Use-create DDBS database in the same server as DDBS_ServerDB database.
                    Shard shard = new Shard("local", RavenContext.ServerDB.Url, "DDBS");
                    shards.Add(shard);
                }
            }

            // Add shards to known stores.
            shardStores = new Dictionary<string, IDocumentStore>();
            foreach (Shard s in shards)
            {
                shardStores.Add(s.Name, new DocumentStore { Url = s.URL, DefaultDatabase = s.DB });
            }

            // Apply sharding strategy.
            shardStrategy = new ShardStrategy(shardStores)
                .ShardingOn<Sample>(s => s.Shard);
            shardStrategy.ShardAccessStrategy = new SequentialShardAccessStrategy(); // Sequential or parallel shard access.

            // Setting OnError to continue when shard is unavailable. (does not work with ParallelShardAccessStrategy)
            shardStrategy.ShardAccessStrategy.OnError += (failingCommands, requestData, exception) =>
            {
                if (requestData.Query == null) // query by id, cannot be handled
                    return false; // raise error

                return true; // continue (return and continue with other shard)
            };

            // Initialize. (does not open connections)
            store = new ShardedDocumentStore(shardStrategy)
                .Initialize();
        }

        // One DB server.
        public RavenDB(string selShard)
        {
            // Load shard list.
            using (IDocumentSession session = RavenContext.CreateSession())
            {
                try
                {
                    shards = session.Query<Shard>().ToList();
                    if (shards.Count == 0) throw new Exception(); // no database in list
                }
                catch {}
            }

            try
            {
                Shard s = shards.Where(shard => shard.Name.Equals(selShard)).First();
                string URL = s.URL;

                store = new DocumentStore()
                {
                    Url = URL,
                    DefaultDatabase = "DDBS"
                }.Initialize();

                //fstore = new FilesStore()
                //{
                //    Url = URL,
                //    DefaultFileSystem = "DDBS_FS"
                //}.Initialize();
            }
            catch(Exception e)
            {
                Trace.WriteLine(e.ToString());
            }
        }

        // Put indexes for each database individualy. (if indexes with same name exists return exception for each)
        // Uses "DatabaseCommands.PutIndex instead of Index().Execute(store) - more precise, low level control.
        public string[] PutIndexes()
        {
            string[] exceptions = new string[shards.Count];
            int count = 0;
            foreach (Shard s in shards)
            {
                try
                {
                    using (IDocumentStore store = new DocumentStore
                    {
                        Url = s.URL,	// server URL
                        DefaultDatabase = s.DB	// default database
                    })
                    {
                        store.Initialize();

                        store.DatabaseCommands.PutIndex("SamplesByTopPeaks", new IndexDefinition
                        {
                            Map = @"from sample in docs.samples
                                let toppeaks = sample.Peaks.OrderByDescending(p => p.V).Take(3)
                                select new
                                {
                                    P1 = toppeaks[0].M,
                                    P2 = toppeaks[1].M,
                                    P3 = toppeaks[2].M,
                                    R1 = toppeaks[0].V / toppeaks[1].V,
                                    R2 = toppeaks[1].V / toppeaks[2].V,
                                };"
                        });
                         
                        store.DatabaseCommands.PutIndex("Map_NameCompanyShard", new IndexDefinition
                        {
                            Map = @"from sample in docs.samples
                                select new
                                {
                                    sample.Name,
                                    sample.Company,
                                    sample.Shard,
                                    Count = 1
                                };",

                            Reduce = @"from result in results
                                    group result by new { result.Name, result.Company, result.Shard } into g
                                    select new
                                    {
                                        Name = g.Key.Name,
                                        Company = g.Key.Company,
                                        Shard = g.Key.Shard,
                                        Count = g.Sum(x => x.Count)
                                    };"
                        });

                        store.DatabaseCommands.PutIndex("SamplesByName", new IndexDefinition
                        {
                            Map = @"from sample in docs.samples
                                    select new { sample.Name };"
                        });

                        store.DatabaseCommands.PutIndex("SamplesByAuthor", new IndexDefinition
                        {
                            Map = @"from sample in docs.samples
                                    select new { sample.Author };"
                        });

                        store.DatabaseCommands.PutIndex("SamplesByCompany", new IndexDefinition
                        {
                            Map = @"from sample in docs.samples
                                    select new { sample.Company };"
                        });

                        store.DatabaseCommands.PutIndex("SampleById", new IndexDefinition
                        {
                            Map = @"from sample in docs.samples
                                    select new { sample.Id };"
                        });

                    }
                    exceptions[count] = "--Indexes created--";
                }
                catch (Exception e)
                {
                    exceptions[count] = e.ToString();
                }
                count++;
            }
            return exceptions;
        }

        public List<String> GetShardNames()
        {
            return shards.Select(s => s.Name).ToList();
        }
    }
}